Getting Granular: How Tableau and Power BI Handle Level of Detail
The Problem: One Chart, Two Granularities
BI tools love to aggregate. Drag a numeric field into a chart, and it happily sums, counts, or averages — usually at the level defined by your visual:
“Sales by Region” means Tableau or Power BI groups your sales records by region, then aggregates them.
This works beautifully… until it doesn’t.
🔍 Real-World Problems That Break the Default Grain
Let’s say you want to show:
% of Total Sales per Category — but the total should be overall, not filtered by Region
Average Sales per Customer — even though you’re visualizing by Product Sub-Category
Top 1 Product per Region — which requires aggregating sales, then slicing by region
Customer Count per Segment, but with filters that don’t apply to that metric
These are common analytical questions — and they all require a different level of detail than the current view.
⚠️ Why This Trips People Up
By default:
Tableau calculates at the visual level of detail (vizLOD)
Power BI evaluates filters based on visual context and slicers
Which means:
You often get a result, but not the one you intended.
Worse: the chart looks fine — until someone asks:
“Wait… why isn’t that total matching what I expected?”
🎯 The Core Problem:
You want to display data at one level… but calculate it at another.
And that’s where tools diverge in how they handle explicit granularity control.
In the next sections, we’ll look at how Tableau uses LOD expressions and context filters, and how Power BI uses DAX filter functions to give you that control.
Tableau’s Approach — LOD Expressions and Context Filters
In Tableau, the default behavior is to aggregate at the level of the visualization.
So if you drag [Sales]
into a view broken down by [Category]
, Tableau will return SUM(Sales)
for each category — simple and automatic.
But what if you want to:
Count unique customers regardless of the view’s granularity?
Calculate sales per customer, then average per region?
Compare a product’s sales to the total across all products, even when filtering?
That’s where Level of Detail (LOD) Expressions come in.
🔹 The LOD Syntax
LOD expressions allow you to override the default level of detail.
{FIXED [Dimension(s)] : AGG([Measure])}
{INCLUDE [Dimension(s)] : AGG([Measure])}
{EXCLUDE [Dimension(s)] : AGG([Measure])}
Let’s walk through each.
✅ {FIXED}
— Lock to a Specific Level
This expression removes the influence of the view’s granularity and uses only the dimensions you specify.
{FIXED [Customer Segment] : COUNTD([Customer ID])}
This counts unique customers per segment, even if your view is showing something else — like products or sub-categories.
You can also use {FIXED : ...}
with no dimension, which gives you a global total:
{FIXED : COUNTD([Customer ID])}
Use this when you want to:
Create a constant denominator for % of total
Create a reference value (e.g., max profit overall)
✅ {INCLUDE}
— Add Dimensions Temporarily
This is like telling Tableau:
“Break this down further than the view does — then aggregate back up.”
Example:
{INCLUDE [Product Name] : AVG([Sales])}
If your view is showing [Region]
, this will calculate the average sales per product, then average those within each region.
Use when:
You want to bring in row-level granularity temporarily
You’re working on ratio or per-item metrics
✅ {EXCLUDE}
— Remove Dimensions from the View
The opposite of INCLUDE. You’re saying:
“I know the view is grouped by X and Y — but calculate as if it was just by X.”
Example:
{EXCLUDE [Region] : SUM([Sales])}
This removes Region from the calculation, even if it’s present in the visual — useful for:
Baselines
Reference lines
High-level metrics in detailed views
⚠️ LOD Expression Gotchas
Filters can interfere with LODs — which leads us to Context Filters
{FIXED}
expressions are evaluated before most filtersIf you want a filter to apply before the LOD expression, it must be set as a Context Filter
🔸 Context Filters
When you want a filter (like [Order Date]
) to affect a {FIXED}
expression, right-click it and choose “Add to Context”. This changes the filter’s evaluation order so it’s applied before the FIXED calculation runs.
If you don’t do this, your numbers may seem “wrong” — when in fact, they’re just not including your filter at the right time.
🧠 Summary
Use Case | Expression Type |
---|---|
Count customers per region | {FIXED [Region] : COUNTD(Customer ID)} |
Compare product sales to total | {FIXED : SUM(Sales)} |
Avg sales per item in view | {INCLUDE [Product Name] : AVG(Sales)} |
Ignore one dimension | {EXCLUDE [Region] : SUM(Sales)} |
🔸 A Real-World Ratio: Most of the Time, It’s FIXED
If you work with Tableau regularly, you’ll notice a pattern:
Roughly 80% of the time, what you need is a
{FIXED}
expression.
You’re locking a metric to a specific business grouping (e.g., customer, region, segment)
You want to isolate the calculation from the view’s grain
You need something stable and reusable across charts
{INCLUDE}
and {EXCLUDE}
have their place — especially for row-level tricks or nested LOD logic —
but in most use cases, FIXED gives you the control and predictability you’re after.
This doesn’t mean you should avoid the other types — just that understanding FIXED deeply will solve most real-world LOD challenges.
Power BI’s Approach — CALCULATE and Context Control
While Tableau gives you explicit keywords to manipulate granularity, Power BI uses a different strategy:
It lets you control the filter context applied to any calculation — dynamically, precisely, and programmatically.
At the core of this logic is DAX’s most important function:
CALCULATE(<expression>, <filter1>, <filter2>, ...)
🧠 What CALCULATE Does
CALCULATE evaluates an expression in a modified filter context.
This means:
You can change what filters apply
You can remove, override, or narrow filters from visuals, slicers, or page filters
You’re not controlling “levels of detail” per se — you’re controlling the filter logic that defines granularity
✅ Common Filter Modifiers in DAX
Let’s look at the key functions you’ll use with CALCULATE()
to handle different levels of context:
DAX Function | What It Does |
---|---|
REMOVEFILTERS() |
Ignores filters from visuals/slicers |
ALL() |
Removes all filters — useful for total / denominator |
ALLEXCEPT() |
Keeps only specified filters, removes others |
KEEPFILTERS() |
Applies filters without replacing existing ones |
FILTER() |
Builds custom row filters inside CALCULATE |
🔍 Examples
🔹 Global unique count of customers (like {FIXED : COUNTD(...)}
in Tableau):
CustomerCountGlobal = CALCULATE(DISTINCTCOUNT('Customers'[CustomerID]), REMOVEFILTERS())
🔹 Count customers per segment:
CustomerPerSegment = CALCULATE(DISTINCTCOUNT('Customers'[CustomerID]))
🔹 Keep only one filter dimension (like Tableau’s {FIXED [Segment] : ...}
):
CustomerPerSegmentOnly = CALCULATE(
DISTINCTCOUNT('Customers'[CustomerID]),
ALLEXCEPT('Customers', 'Customers'[Segment])
)
This says: “Remove all filters except Segment” — even if the visual also includes Region or Product.
⚠️ Things to Watch For
DAX has row context and filter context — and they’re evaluated differently. This trips people up fast.
You must explicitly control context for complex calculations. There is no “LOD” keyword — it’s all logic.
It’s easy to make the formula work, but hard to explain why if you’re not careful with variable naming and structuring.
✍️ Practical Patterns
Use Case | Tableau | Power BI |
---|---|---|
Global % of Total | {FIXED : SUM(Sales)} |
CALCULATE(SUM(Sales), REMOVEFILTERS()) |
Segment-level count | {FIXED [Segment] : COUNTD(ID)} |
CALCULATE(DISTINCTCOUNT(ID), ALLEXCEPT(...)) |
Top N inside group | FIXED with rank filter | RANKX + CALCULATE(...) per group |
Metric ignoring a slicer | FIXED LOD + context filter | REMOVEFILTERS(SlicerTable[Field]) |
🧠 Key Takeaway for Power BI Users
You’re not thinking in terms of “level of detail” — you’re thinking in filter logic.
Power BI doesn’t show you what level it’s summarizing at — it just responds to whatever filters (visual, page, slicer) are in play.
So your job is to override those filters when needed.
Final Thoughts & Takeaways
When we talk about “level of detail,” we’re really talking about control.
Most of the time, BI tools do a good job guessing how you want to aggregate your data. But when you start asking more specific, more meaningful questions — “How many customers are we serving regardless of category?” or “What was the average per transaction, not per visual group?” — you need to take control.
And that’s where things diverge.
🔷 Tableau’s Approach
Tableau gives you straightforward LOD expressions (
FIXED
,INCLUDE
,EXCLUDE
) that are explicit, readable, and scoped.You say what you want to include or ignore — and Tableau does it.
In most real-world scenarios,
FIXED
covers 80% of use cases.
Its strength is clarity — but you do need to understand context filters and evaluation order when things get tricky.
🔶 Power BI’s Approach
Power BI uses DAX functions like
CALCULATE()
, paired with filter functions likeALL()
,REMOVEFILTERS()
, andALLEXCEPT()
.Instead of writing one-off LOD logic, you modify the filter context in which your measure is evaluated.
It’s more flexible, but also more verbose and less intuitive at first.
Its strength is power — but you must deeply understand how row context and filter context interact.
🧠 What They Have in Common
Both tools make assumptions about aggregation unless you tell them otherwise.
Both allow you to calculate at different levels than what’s shown in the visual.
Both reward you for thinking clearly about intent — what is the grain of your calculation?
✍️ Final Tip
Don’t just ask “what do I want to show?”
Ask: “At what level should this number be calculated?”
That shift in thinking separates good dashboard builders from great analysts.